package com.example.testdemo;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;


import java.lang.reflect.Method;
import java.util.*;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class Mocktest {


    /**使用mock模拟对象，你可以mock具体的类型,不只是接口
     *mock函数得到了一个代理对象，调用这个对象的函数时，默认情况下返回值都是null(不论有没有返回值)，
     *如果基本类型，默认值是0或者false。*/
    @Test
    public void verify_behaviour(){
        //模拟创建一个List对象
        List mock = mock(List.class);
        //做展示用，可以mock自己创建的对象
        Arithmetic amock =mock(Arithmetic.class);
        //使用mock的对象
        mock.add(1);
        //输出分别是null和0
        System.out.println(mock.get(0));
        System.out.println(amock.add());

      /*  //验证add(1)和clear()行为是否发生，如果没有发生会被抛出异常
        verify(mock).add(1);
        verify(mock).clear();*/
    }

    /**当测试的单元依赖这个mock对象的返回值时，我们可以通过提前申明这个函数的返回值来测试各种各样的场景。
     * 使用when(方法名).thenReturn(返回值);来预设方法，我们称之为方法打桩
     * 存根可以被覆盖
     * 没有存根的话，就根据之前提到的默认返回值的方法来对待
     */
    @Test
    public void when_thenReturn(){
        List mockedList  = mock(List.class);
        //调用get(0)时，返回first
        when(mockedList.get(0)).thenReturn("first");
        //调用get(1)时，直接抛出异常
        //when(mockedList.get(1)).thenThrow(new RuntimeException());

        //返回first
        System.out.println(mockedList.get(0));
        //没有存根，则会返回null
        System.out.println(mockedList.get(999));
        //抛出异常
        //System.out.println(mockedList.get(1));

        //连续存根,会将连续的存根记录下来参数按顺序出现,如果之前设置了会被覆盖掉
        when(mockedList.get(0)).thenReturn(0).thenReturn(1).thenReturn(2);

        //值为0,1,2,2
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        System.out.println(mockedList.get(0));
        //前面用尽之后，存根值为最后一个
        System.out.println(mockedList.get(0));
    }

    /**调用某个函数的时候，执行一个回调函数。
     * thenAnswer(new Answer<Object>
     * 主要用途比如说根据参数不同返回不同的结果
     */
    @Test
    public void mockList() {
        List mockedList = mock(List.class);
        //anyInt()的意思的任意整形 thenAnswer函数是回调函数的意思
        when(mockedList.get(anyInt())).thenAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                System.out.println("执行回调函数");
                //当前回调函数的参数
                Object[] arguments = invocationOnMock.getArguments();
                System.out.println("参数为:" + Arrays.toString(arguments));
                //当前回调函数的方法名
                Method method = invocationOnMock.getMethod();
                System.out.println("方法名为:" + method.getName());

                return "输出自己想要的返回值";
            }
        });

        System.out.println(mockedList.get(2));
    }



    /** verify.mock对象.方法  用途是检测这个方法是否调用规定次数，没有的话就会报错
     *   可以如同方法中那样设置次数
     *  另一个这段代码要注意的是 参数匹配器 anyInt()就是参数匹配器，同理还得有很多匹配器
     *  参数匹配器可以自定义，一个函数接收多个参数时，如果其中有一个用了参数匹配器，那其他的参数也必须用
     */
    @Test
    public void vmockList() {
        List mockedList  = mock(List.class);
        when(mockedList.get(0)).thenReturn("first");
        //返回first
        System.out.println(mockedList.get(0));

        //验证是否调用过get函数。
        verify(mockedList).get(anyInt());

        System.out.println(mockedList.get(0));
        verify(mockedList, times(2)).get(anyInt());
    }



    /** 使用注解定义会比代码定义简单很多，推荐使用
     * 缺点是不能在方法内定义
     */
    @Mock
    private List zmockedList;
    @Test
    public void zmockList() {
        zmockedList.add("one");
        verify(zmockedList).add("one");
    }

    /** Spy（间谍）
     * mock是创建了一个假对象
     * Spy是针对于“真实存在”的对象。在重构已有的旧代码时，Spy会比较好用。
     * spy监听的是真实的对象，在操作真实对象的时候可能会出现越界之类的问题
     */
    @Test
    public void spyList() {
        //申请了一个真实的对象
        List list = new LinkedList();
        List spy = spy(list);
        //可以选择存根某些函数
        when(spy.size()).thenReturn(100);
        //调用真实的方法
        spy.add("one");
        spy.add("two");
        //打印第一个元素
        System.out.println(spy.get(0));
        //获取list的大小
        System.out.println(spy.size());
        //验证
        verify(spy).add("one");
        verify(spy).add("two");
    }


    /** 自动实例化 @InjectMocks
     * 被测试类标注为@InjectMocks时，会自动实例化，并且把@Mock或者@Spy标注过的依赖注入进去。
     */



}
